// CyTargetLinker,
// a Cytoscape plugin to extend biological networks with regulatory interaction
// Copyright 2011-2012 BiGCaT Bioinformatics
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package org.cytoscape.cytargetlinker.internal;


import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.cytoscape.cytargetlinker.internal.io.XgmmlParser;
import org.cytoscape.cytargetlinker.internal.model.RegNet;
import org.cytoscape.cytargetlinker.internal.util.Preferences;
import org.cytoscape.model.CyEdge;
import org.cytoscape.model.CyNode;
import org.cytoscape.view.model.CyNetworkView;
import org.cytoscape.view.model.CyNetworkViewFactory;
import org.cytoscape.view.model.View;
import org.cytoscape.view.presentation.property.BasicVisualLexicon;


/**
 * this class handles the RINs
 * load a RIN
 * show or hide a RIN
 * 
 */
public class RINHandler {
	
	private static RINHandler handler;
	
	public static RINHandler getHandler() {
		if(handler == null) {
			handler = new RINHandler();
		}
		return handler;
	}
	
	private RINHandler() {
		xPosition = new HashMap<Long, Double>();
		yPosition = new HashMap<Long, Double>();
		removedNodes = new ArrayList<CyNode>();
		doNotRemove = new ArrayList<CyNode>();
	}
	
	/**
	 * loads and opens the rins
	 * TODO: allow other file types than xgmml
	 */
	public void loadNetworks(List<File> files, CTLManager linker) {
		for(File file : files) {
			XgmmlParser parser = new XgmmlParser();
			RegNet network = new RegNet();
			try {
				parser.parseXgmmlFile(file, new ArrayList<String>(linker.getUserNetworkIds().keySet()));
				network.setName(parser.getNetworkName());
				network.setFile(file);
				network.setEdges(parser.getEdges());
				network.setNewInteractions(parser.getEdges().size());
				network.setEdgeType(parser.getNetworkProperties().get("Type"));
				if(network.getNewInteractions() == 0) {
					linker.setStatusMessage(linker.getStatusMessage() + network.getName() + " | " + "no matching regulatory interactions found.\n");
				} else {
					linker.setStatusMessage(linker.getStatusMessage() + network.getName() + " | " + network.getNewInteractions() + " regulatory interactions.\n");
					linker.getRins().add(network);
				}
			} catch (Exception e) {
				linker.setStatusMessage(linker.getStatusMessage() + "WARNING: An error occured when reading " + file.getName() + ". This RIN will not be included in the analysis.\n");
			} 
		}
		
		Collections.sort(linker.getRins());
		for(int j = 0; j < linker.getRins().size(); j++) {
			linker.getRins().get(j).setColor(Preferences.getInstance().getColors().get(j));
		}
	}
	
	/**
	 * hides all edges from one regulatory interaction network
	 * if there are not any other interactions for a new node
	 * then the node is hidden as well
	 */
	public void hideRIN(RegNet net, CTLManager linker) {
		
		CyNetworkViewFactory viewFactory = linker.getNetViewFactory();
		CyNetworkView view = viewFactory.createNetworkView(linker.getResultingNetwork());
	
		for (CyNode node : net.getCyNodes()) {
			Long id = node.getSUID();
			if(removedNodes.contains(node)) {
				if (!net.getHiddenNodes().contains(node)) {
					net.getHiddenNodes().add(node);
				}
				net.getxPosition().put(id, xPosition.get(id));
				net.getyPosition().put(id, yPosition.get(id));
			} else {
				// check if other edge still points to the node
				boolean found = false;
				for (RegNet rin : linker.getRins()) {
					if (!rin.equals(net)) {
						if (rin.getCyNodes().contains(node) && !rin.getHiddenNodes().contains(node)) {
							found = true;
						}
					}
				}
				if (!net.getHiddenNodes().contains(node)) {
					View<CyNode> nodeView = view.getNodeView(node);
					
					net.getxPosition().put(id, nodeView.getVisualProperty(BasicVisualLexicon.NODE_X_LOCATION));
					net.getyPosition().put(id, nodeView.getVisualProperty(BasicVisualLexicon.NODE_Y_LOCATION));
				}
	
				if (!found && !net.getHiddenNodes().contains(node)) {
					view.getNodeView(node).setVisualProperty(BasicVisualLexicon.NODE_VISIBLE, false);	
				}
				if (!net.getHiddenNodes().contains(node)) {
					net.getHiddenNodes().add(node);
				}
			}
		}
		net.getCyNodes().removeAll(net.getHiddenNodes());

		for (CyEdge edge : net.getCyEdges()) {
			view.getEdgeView(edge).setVisualProperty(BasicVisualLexicon.EDGE_VISIBLE, false);
			net.getHiddenEdges().add(edge);
		}
		net.getCyEdges().clear();
		net.setHidden(true);
		
		linker.getVisualStyle().apply(view);
		view.updateView();
	}
	
	/**
	 * shows all the hidden edges and nodes from a RIN again
	 * @param net
	 */
	public void showRIN(RegNet net, CTLManager linker) {
		CyNetworkViewFactory viewFactory = linker.getNetViewFactory();
		CyNetworkView view = viewFactory.createNetworkView(linker.getResultingNetwork());
		
		for(CyNode node : net.getHiddenNodes()) {
			view.getNodeView(node).setVisualProperty(BasicVisualLexicon.NODE_VISIBLE, true);	
			
			View<CyNode> nodeView = view.getNodeView(node);
            if(nodeView.getVisualProperty(BasicVisualLexicon.NODE_X_LOCATION) == 10.0 
            		&& nodeView.getVisualProperty(BasicVisualLexicon.NODE_Y_LOCATION) == 10.0) {
            	nodeView.setVisualProperty(BasicVisualLexicon.NODE_X_LOCATION, net.getxPosition().get(node.getSUID()));
            	nodeView.setVisualProperty(BasicVisualLexicon.NODE_Y_LOCATION, net.getyPosition().get(node.getSUID()));
            }
            net.getCyNodes().add(node);
		}
		net.getxPosition().clear();
		net.getyPosition().clear();
		net.getHiddenNodes().clear();
        
		for (CyEdge edge: net.getHiddenEdges()){ 
			view.getEdgeView(edge).setVisualProperty(BasicVisualLexicon.EDGE_VISIBLE, true);
			net.getCyEdges().add(edge);
        } 
		net.setHidden(false);
		net.getHiddenEdges().clear();
			
		linker.getVisualStyle().apply(view);
		view.updateView();
	}
	
	
	private Map<Long, Double> xPosition; 
    private Map<Long, Double> yPosition; 
    private List<CyNode> removedNodes;
    private List<CyNode> doNotRemove;
    
    public List<CyNode> getRemovedNodes() {
    	return removedNodes;
    }

	
	/**
	 * hides all interactions between two nodes if not
	 * enough RINs support the interaction
	 */
	public void applyThreshold(CTLManager linker) {
		CyNetworkViewFactory viewFactory = linker.getNetViewFactory();
		CyNetworkView view = viewFactory.createNetworkView(linker.getResultingNetwork());
		
		Map<String, List<CyEdge>> map = new HashMap<String, List<CyEdge>>();
		
		// check if a node is hidden - then it's not necessary anymore to remove it
		for(RegNet net : linker.getRins()) {
			if(net.isHidden()) {
				for(CyNode n : net.getHiddenNodes()) {
					if(removedNodes.contains(n)) {
						removedNodes.remove(n);
					}
				}
			}
		}
		
		// restore all removed nodes before applying the threshold
		for(CyNode node : removedNodes) {
			view.getNodeView(node).setVisualProperty(BasicVisualLexicon.NODE_VISIBLE, true);	
			
			View<CyNode> nodeView = view.getNodeView(node);
            if(nodeView.getVisualProperty(BasicVisualLexicon.NODE_X_LOCATION) == 10.0 
            		&& nodeView.getVisualProperty(BasicVisualLexicon.NODE_Y_LOCATION) == 10.0) {
            	nodeView.setVisualProperty(BasicVisualLexicon.NODE_X_LOCATION, xPosition.get(node.getSUID()));
            	nodeView.setVisualProperty(BasicVisualLexicon.NODE_Y_LOCATION, yPosition.get(node.getSUID()));
            }
		}
		
		removedNodes.clear();
		doNotRemove.clear();
		xPosition.clear();
		yPosition.clear();
		
		// restore all edges before applying the threshold
		for(RegNet net : linker.getRins()) {
			if(!net.isHidden()) {
				for(CyEdge e : net.getCyEdges()) {
					view.getEdgeView(e).setVisualProperty(BasicVisualLexicon.EDGE_VISIBLE, true);
	
					String key = e.getSource().getSUID() + " - " + e.getTarget().getSUID();
					if(map.containsKey(key)) {
						map.get(key).add(e);
					} else {
						List<CyEdge> list = new ArrayList<CyEdge>();
						list.add(e);
						map.put(key, list);
					}
				}
			}
		}

		// go through map and hide all edges and nodes which 
		// are supported by less RINs than the threshold requires
		for(String str : map.keySet()) {
			if(map.get(str).size() < linker.getThreshold()) {
				for(CyEdge e : map.get(str)) {
					view.getEdgeView(e).setVisualProperty(BasicVisualLexicon.EDGE_VISIBLE, false);
					
					if(linker.getNewNodes().containsKey(e.getSource().getSUID())) {
						removeNode(e.getSource(), view);
					} else if (linker.getNewNodes().containsKey(e.getTarget().getSUID())) {
						removeNode(e.getTarget(), view);
					}
				}
			} else {
				for(CyEdge e : map.get(str)) {
					doNotRemove.add(e.getSource());
					doNotRemove.add(e.getTarget());
				}
			}
		}
		
		removedNodes.removeAll(doNotRemove);
		
		for(CyNode node : removedNodes) {
			view.getNodeView(node).setVisualProperty(BasicVisualLexicon.NODE_VISIBLE, false);
		}
	}
	
	private void removeNode(CyNode node, CyNetworkView view) {
		if(!removedNodes.contains(node)) {
			removedNodes.add(node);
			
			View<CyNode> nodeView = view.getNodeView(node);
			
			xPosition.put(node.getSUID(), nodeView.getVisualProperty(BasicVisualLexicon.NODE_X_LOCATION));
			yPosition.put(node.getSUID(), nodeView.getVisualProperty(BasicVisualLexicon.NODE_Y_LOCATION));
		}
	}

}
